/* 
 * File:   SceneParser.cpp
 * Author: brady
 * 
 * Created on March 25, 2011, 6:18 PM
 */

#include "SceneParser.h"


SceneParser::SceneParser() {
}

SceneParser::SceneParser(const SceneParser& orig) {
}

SceneParser::~SceneParser() {
}

SceneParser::SceneParser(string inputFile)
{
    this->inputFile = inputFile;
}

Scene& SceneParser::Parse()
{
    /*
     * bite should contain a meaningful string before every call in this method.
     * ALWAYS read from file into bite
     */


    this->in_file.open(inputFile.c_str(), ios_base::in);
    if(!in_file.is_open()) {
        cout << "File not found\n";
        exit(1);
    };

    //BEGIN PARSING

    cout << "Parsing Scene File...\n";
    in_file >> bite;
    while(!in_file.eof())
    {
        //should always begin this loop with bite being one of the parsable options
        if(!ValidOption(bite))
        {
            cout << "Invalid scene option: " << bite <<"\n";
            exit(1);
        }
        int cnum = 0;
        if(bite == "shape") cnum = 1;
        if(bite == "light") cnum = 2;
        if(bite == "camera") cnum = 3;
        if(bite  == "instance") cnum = 4;
        switch(cnum)
        {
            case 1 :
            {
                Shape* sh = BuildShape();
                if(sh->InstanceId == "") scene.AddShape(sh);
            }
            break;
            case 2 :
            {
                scene.AddLight(BuildLight());
            }
            break;
            case 3 :
            {
                scene.AddCamera(BuildCamera());
            }
            break;
            case 4:
            {
                scene.AddShape(BuildInstance());
            }
            break;
            default:
                in_file >> bite;
                break;
        }
    }
    cout <<"Done\n";
    return scene;
}

    
Shape* SceneParser::BuildShape()
{
    in_file >> bite;
    int cnum = 0;
    if(bite == "triangle") cnum = 1;
    if(bite == "sphere") cnum = 2;
    if(bite == "mesh") cnum = 3;
    if(bite == "construct") cnum = 4;
    if(bite == "box") cnum = 5;

    Shape* shape;
    switch(cnum)
    {
        case 1:
        {
            shape = BuildTriangle();
            break;
        }
        case 2:
        {
            shape = BuildSphere();
            break;
        }
        case 3:
        {
            shape = BuildMesh();
            break;
        }
        case 4:
        {
            shape = BuildConstruct();
            break;
        }
        case 5:
        {
            shape = BuildBox();
            break;
        }
        case 0:
        {
            cout << "Invalid shape "<< bite <<endl;
            exit(1);
        }
    }
    return shape;
}
Shader* SceneParser::BuildShader()
{
    Shader* shader;
    Texture* tx;
     if(bite == "lambertian")
        {
            in_file >> bite;
            tx = BuildTexture();
            LambertianShader* l = new LambertianShader();
            l->SetTexture(tx);
            shader = l;
            in_file >> bite;
        }
        if(bite == "blinnphong")
        {
            BlinnPhongShader* bps = new BlinnPhongShader();
            double sr,sg,sb, m, pe, gloss;
            in_file >> bite;
            tx = BuildTexture();
            bps->SetTexture(tx);
            in_file >> sr >> sg >> sb >> pe;
            bps->SetSpecular(Vector3D(sr, sg, sb));
            bps->SetPhongExponent(pe);
            m = 0.0;
            gloss = 0.0;
            in_file >> bite;
            while(bite == "m" || bite == "g")
            {
                if(bite == "m")
                {
                    in_file >> m;
                    bps->SetMirrorCoefficient(m);
                }
                else if(bite == "g")
                {
                    in_file >> gloss;
                    bps->SetGlossValue(gloss);
                }
                in_file >> bite;
            }
            shader = bps;
        }
        if(bite == "dielectric")
        {
            in_file >> bite;
            tx = BuildTexture();
            double sr,sg,sb, m, gloss, pe, t, rIndex;
            in_file >> sr >> sg >> sb >> pe;
            DielectricShader* ds = new DielectricShader();
            ds->SetSpecular(Vector3D(sr,sg,sb));
            ds->SetPhongExponent(pe);
            ds->SetTexture(tx);
            m = 0.0;
            t = 0.0;
            gloss = 0.0;
            rIndex = 1.0;
            in_file >> bite;
            while(bite == "m" || bite == "t" || bite == "r" || bite == "g")
            {
                if(bite == "m")
                {
                    in_file >> m;
                    ds->SetMirrorCoefficient(m);
                }
                else if(bite == "t")
                {
                    in_file >> t;
                    ds->SetTransparency(t);
                }
                else if(bite == "r")
                {
                    in_file >> rIndex;
                    ds->SetRIndex(rIndex);
                }
                else if(bite == "g")
                {
                    in_file >> gloss;
                    ds->SetGlossValue(gloss);
                }
                in_file >> bite;
            }  
            shader = ds;
        }
    return shader;
}

Texture* SceneParser::BuildTexture()
{
    if(bite == "checkerboard")
    {
        double checkWidth, r1,g1,b1,r2,g2,b2;
        in_file >> checkWidth >> r1 >> g1 >> b1 >> r2 >> g2 >> b2;
        return new CheckerboardTexture(checkWidth, Vector3D(r1,g1,b1), Vector3D(r2,g2,b2));
    }
    else if(bite == "perlin")
    {
        int octaves;
        double r1,g1,b1,r2,g2,b2;
        in_file >> octaves >> r1 >> g1 >> b1 >> r2 >> g2 >> b2;
        PerlinTexture* pt = new PerlinTexture(Vector3D(r1,g1,b1), Vector3D(r2,g2,b2), octaves);
        return pt;
    }
    else if (bite == "wood")
    {
        WoodTexture* wt = new WoodTexture();
        return wt;
    }
    //any additional textures should be read here
    else //it's just a color
    {
        double r,g,b;
        r = atof(bite.c_str()); //bite was actually the first color value
        in_file >> g >> b;
        return new ColoredTexture(Vector3D(r,g,b));
    }
}


Light* SceneParser::BuildLight()
{
    double x, y, z, r, g, b;
    in_file >> x >> y >> z >> r >> g >> b;
    Light* l = new Light(Vector3D(x, y, z), Vector3D(r, g, b));
    in_file >> bite;
    if(bite == "s")
    {
        double size;
        in_file >> size;
        l->SetSize(size);
        in_file >> bite;
    }
    return l;
}
Camera* SceneParser::BuildCamera()
{
    double px, py, pz, gx, gy, gz, fl, imgpw;
    string style = "viewdir";
    in_file >> px >> py >> pz;
    in_file >> bite;
    if(bite == "viewir" || bite == "lookatpt")
    {
        style = bite;
        in_file >> gx >> gy >> gz;
    }
    else
    {
        //didn't specify camera style, assume viewdir, and that we just read gx
        gx = atof(bite.c_str());
        in_file >> gy >> gz;
    }
    in_file >> fl >> imgpw;
    Camera* cam;
    if(style == "lookatpt")
    {
        cam = new Camera(Vector3D(px, py, pz), Vector3D(gx - px, gy - py, gz - pz));
    }
    else{
        cam = new Camera(Vector3D(px, py, pz), Vector3D(gx, gy, gz));
    }
    cam->SetFocalLength(fl);
    cam->SetFrameWidth(imgpw);
    in_file >> bite;
    return cam;
}

Shape* SceneParser::GetInstanceShape(string instanceId)
{
    for(int i = 0; i<InstanceShapes.size(); i++)
    {
        if(InstanceShapes[i]->InstanceId == instanceId)
        {
            return InstanceShapes[i];
        }
    }
    return NULL;
}

void SceneParser::RenderBoundingBox(Shape* s)
{
    vector<Vector3D> points;
    double xMin, xMax, yMin, yMax, zMin, zMax;
    xMin = s->GetBoundingBox()->GetXMin();
    xMax = s->GetBoundingBox()->GetXMax();
    yMin = s->GetBoundingBox()->GetYMin();
    yMax = s->GetBoundingBox()->GetYMax();
    zMin = s->GetBoundingBox()->GetZMin();
    zMax = s->GetBoundingBox()->GetZMax();

    Vector3D A, B, C, D, E, F, G, H;
    
    A = Vector3D(xMin, yMin, zMin);
    B = Vector3D(xMax, yMin, zMin);
    C = Vector3D(xMin, yMin, zMax);
    D = Vector3D(xMax, yMin, zMax);
    E = Vector3D(xMin, yMax, zMin);
    F = Vector3D(xMax, yMax, zMin);
    G = Vector3D(xMin, yMax, zMax);
    H = Vector3D(xMax, yMax, zMax);


    points.push_back(A);
    points.push_back(B);
    points.push_back(C);
    points.push_back(D);
    points.push_back(E);
    points.push_back(F);
    points.push_back(G);
    points.push_back(H);

    Shader* cornerShader = new LambertianShader(1.0, 1.0, 0.0);
    for(int i = 0; i<points.size(); i++)
    {
        Sphere* sph = new Sphere(points[i], 0.1);
        sph->SetShader(cornerShader);
        scene.AddShape(sph);
    }

}

    Shape* SceneParser::BuildTriangle()
    {
        double p1x, p1y, p1z, p2x, p2y, p2z, p3x, p3y, p3z;
        in_file >> p1x >> p1y >> p1z >> p2x >> p2y >> p2z >> p3x >> p3y >> p3z;
        Vector3D p1(p1x , p1y , p1z );
        Vector3D p2(p2x , p2y , p2z );
        Vector3D p3(p3x , p3y , p3z );
        Triangle* t = new Triangle(p1, p2, p3);
        in_file >> bite;
        t->SetShader(BuildShader());
        if(bite == "instanceid")
        {
            in_file >> bite;
            t->InstanceId = bite;
            InstanceShapes.push_back(t);
            in_file >> bite;
        }
        return t;
    }
    Shape* SceneParser::BuildSphere()
    {
         double x, y, z, r;
        in_file >> x >> y >> z >> r;
        Sphere* s = new Sphere(Vector3D(x, y, z), r);
        in_file >> bite;
        s->SetShader(BuildShader());
        if(bite == "instanceid")
        {
            in_file >> bite;
            s->InstanceId = bite;
            InstanceShapes.push_back(s);
            in_file >> bite;
        }
        return s;
    }
    
    Shape* SceneParser::BuildBox()
    {
        double x, y, z, w, l, h;
        in_file >> x >> y >> z >> w >> h >> l;
        double wh, lh, hh;
        wh = w/2.0;
        lh = l/2.0;
        hh = h/2.0;
        Vector3D tlf, blf, trf, brf, tlb, blb, trb, brb;
        tlf = Vector3D(x-wh, y+hh, z-lh);
        blf = Vector3D(x-wh, y-hh, z-lh);
        trf = Vector3D(x+wh, y+hh, z-lh);
        brf = Vector3D(x+wh, y-hh, z-lh);
        tlb = Vector3D(x-wh, y+hh, z+lh);
        blb = Vector3D(x-wh, y-hh, z+lh);
        trb = Vector3D(x+wh, y+hh, z+lh);
        brb = Vector3D(x+wh, y-hh, z+lh);
        Mesh* m = new Mesh();
        //front face
        m->AddTriangle(tlf, trf, brf);
        m->AddTriangle(tlf, brf, blf);
        //left side
        m->AddTriangle(tlb, blf, blb);
        m->AddTriangle(tlb, tlf, blf);
        //right side
        m->AddTriangle(trb, brb, brf);
        m->AddTriangle(trb, brf, trf);
        //back face
        m->AddTriangle(tlb, brb, trb);
        m->AddTriangle(tlb, blb, brb);
        //top
        m->AddTriangle(tlf, tlb, trb);
        m->AddTriangle(tlf, trb, trf);
        //bottom
        m->AddTriangle(blf, brb, blb);
        m->AddTriangle(blf, brf, brb);
        in_file >> bite;
        m->SetShader(BuildShader());
        m->BuildBVH();
        if(bite == "instanceid")
                {
                    in_file >> bite;
                    m->InstanceId = bite;
                    InstanceShapes.push_back(m);
                    in_file >> bite;
                }
        return m;

    }

    Shape* SceneParser::BuildMesh()
    {
    Mesh *mesh = new Mesh();
        vector<Vector3D> points;
        int fcount = 0;
        int vcount = 0;
        in_file >> bite;
        if(bite == "v")
        {
            while(bite == "v" || bite == "f"){
                if(bite == "v")
                {
                    double x, y, z;
                    in_file >> x >> y >> z;
                    points.push_back(Vector3D(x, y, z));
                    vcount++;
                }
                if(bite == "f")
                {
                    int p1, p2, p3;
                    in_file >> p1 >> p2 >> p3;
                    mesh->AddTriangle(points[p1-1], points[p2-1], points[p3-1]);
                    fcount++;
                }
                in_file >> bite;
            }
        }
        else
        {
            fstream objStr;
            objStr.open(bite.c_str(), ios_base::in);
            if(!objStr.is_open()) cout << "Failed to open Obj File\n";
            while(!objStr.eof()){
                objStr >> bite;
                if(bite == "v")
                {
                    double x, y, z;
                    objStr >> x >> y >> z;
                    points.push_back(Vector3D(x, y, z));
                    vcount++;
                    continue;
                }
                if(bite == "f")
                {
                    int p1, p2, p3;
                    objStr >> p1 >> p2 >> p3;
                    mesh->AddTriangle(points[p1-1], points[p2-1], points[p3-1]);
                    fcount++;
                    continue;
                }
            }
            in_file >> bite;
        }
        cout << "Building Mesh BVH... \n";
        mesh->BuildBVH();
        cout << " done" << endl;
        mesh->SetShader(BuildShader());
        if (bite == "instanceid") {
            in_file >> bite;
            mesh->InstanceId = bite;
            InstanceShapes.push_back(mesh);
            in_file >> bite;
        }
        return mesh;
    }

    Shape* SceneParser::BuildInstance()
{
    //bite should be "instance"
    in_file >> bite;
    //bit is now the instanceId
    Shape* ishape = GetInstanceShape(bite);
    Instance* instance = new Instance(ishape);
    in_file >> bite;
    //now has shader
    if(ValidShader(bite)){
        instance->SetShader(BuildShader());
    }
    if(bite == "transform")
    {
        stack<string> opStack;
        stack<string> argStack;
        in_file >> bite;
        while(bite == "rotX" || bite == "rotY" || bite == "rotZ"
                || bite == "translate"  || bite == "scale")
        {
            if(bite == "rotX")
            {
                opStack.push("rotX");
                string degs;
                in_file >> degs;
                argStack.push(degs);
                in_file >> bite;
            }

            if(bite == "rotY")
            {
                opStack.push("rotY");
                string degs;
                in_file >> degs;
                argStack.push(degs);
                in_file >> bite;
            }

            if(bite == "rotZ")
            {
                opStack.push("rotZ");
                string degs;
                in_file >> degs;
                argStack.push(degs);
                in_file >> bite;
            }
            if(bite == "translate")
            {
                opStack.push("translate");
                string args;
                string arg1, arg2, arg3;
                in_file >> arg1 >> arg2 >> arg3;
                args = arg1 + " " + arg2 + " " + arg3;
                argStack.push(args);
                in_file >> bite;
            }
            if(bite == "scale")
            {
                opStack.push("scale");
                string args;
                string arg1, arg2, arg3;
                in_file >> arg1 >> arg2 >> arg3;
                args = arg1 + " " + arg2 + " " + arg3;
                argStack.push(args);
                in_file >> bite;
            }
        }
        while(!opStack.empty())
        {
            stringstream argss;
            string op = opStack.top();
            argss.str(argStack.top());
            opStack.pop();
            argStack.pop();
            if(op == "rotX")
            {
                double degs;
                argss >> degs;
                instance->TMatrix.Rotate(degs, TransformMatrix::X_AXIS);
            }

            if(op == "rotY")
            {
                double degs;
                argss >> degs;
                instance->TMatrix.Rotate(degs, TransformMatrix::Y_AXIS);
            }

            if(op == "rotZ")
            {
                double degs;
                argss >> degs;
                instance->TMatrix.Rotate(degs, TransformMatrix::Z_AXIS);
            }
            if(op == "translate")
            {
                double tx, ty, tz;
                argss >> tx >> ty >> tz;
                instance->TMatrix.Translate(tx, ty, tz);

            }
            if(op == "scale")
            {
                double sx, sy, sz;
                argss >> sx >> sy >> sz;
                instance->TMatrix.Scale(sx, sy, sz);
            }
        }
    }
    instance->Finalize();
  //  RenderBoundingBox(instance);
    return instance;
}

    Shape* SceneParser::BuildConstruct()
    {
        Construct* c = new Construct();
        in_file >> bite;
        if(bite == "shape")
        {
            c->SetLeftShape(BuildShape());
        }
        else if(bite == "instance")
        {
            c->SetLeftShape(BuildInstance());
        }
        else
        {
            cout << "Invalid shape "<<bite <<endl;
            exit(1);
        }

        if(bite == "union")
            c->SetOperation(Construct::UNION);

        else if(bite == "difference")
            c->SetOperation(Construct::DIFFERENCE);

        else if(bite == "intersection")
            c->SetOperation(Construct::INTERSECTION);
        else{
            cout <<"Invalid operation "<< bite << endl;
            exit(1);
        }

        in_file >> bite;
        if(bite == "shape")
        {
            c->SetRightShape(BuildShape());
        }
        else if (bite == "instance")
        {
            c->SetRightShape(BuildInstance());
        }
        else
        {
            cout << "Invalid shape "<<bite <<endl;
            exit(1);
        }
        c->SetShader(BuildShader());
        if(bite == "instanceid")
        {
            in_file >> bite;
            c->InstanceId = bite;
            InstanceShapes.push_back(c);
            in_file >> bite;
        }


        c->Finalize();
        return c;
    }